package optimizer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map.Entry;

import simulator.*;

public class OptimizerUtilities {
	
	private OptimizerUtilities () {
		
	}
	
	// finds all GameAssets that would contribute to reaching a target
	// the maximum target should be used to allow for variations that go beyond the minimum target but might
	// increase the time in which the minimum target get's reached
	static public LinkedList<GameAsset> getAssetsContributingToReachingTarget(GameState current, GameState maxTarget) {
		LinkedList<GameAsset> relevantAssets = new LinkedList<GameAsset>();
		for (GameAsset gameAsset : GameAsset.values()) {
			// only assets that the maximum target contains more of than the current state are taken into account
			// this rules out assets that are not contained in the maximum target, since the current state won't contain
			// less than 0 of any asset and it rules out assets where the current state has already reached the maximum target
			if(current.getNumberOfAsset(gameAsset) < maxTarget.getNumberOfAsset(gameAsset)){
				relevantAssets.add(gameAsset);
			}
		}
		return relevantAssets;
	}
	
	static public boolean isAchievedInMinimumTarget (GameState current, GameState minTarget, GameAsset gameAsset){
		if(current.getTotalNumberOfAssets(gameAsset) < minTarget.getNumberOfAsset(gameAsset)){
			return false;
		}
		return true;
	}
	
	// creates a list of assets that are target relevant and can't be afforded at the moment
	// if all relevant assets can be afforded, it returns an empty list
	static public LinkedList<GameAsset> assetsThatNeedSaving (GameState current, GameState maxTarget){
		
		LinkedList<GameAsset> relevantAssets = getAssetsContributingToReachingTarget(current, maxTarget);
		LinkedList<GameAsset> assetsThatNeedSaving = new LinkedList<GameAsset>();
		
//		if(relevantAssets.contains(GameAsset.SUPPLY_DEPOT)){
//			if(calculateSupplyDeficit(getGameAssetArrayFromGameState(current)) > 2){
//				relevantAssets.remove(GameAsset.SUPPLY_DEPOT);
//			}
//		}
		
		int currentMinerals = current.getNumberOfAsset(GameAsset.MINERAL);
		int currentGas = current.getNumberOfAsset(GameAsset.VESPENE_GAS);
		
		GameObject object;
		
		for (GameAsset gameAsset : relevantAssets) {
			object = GameDataStore.getGameObjectFromGameAsset(gameAsset);
			if((object != null) && ((object.getMineralCost() > currentMinerals) || (object.getVespeneCost() > currentGas))){
				assetsThatNeedSaving.add(gameAsset);
			}
		}
		return assetsThatNeedSaving;
	}
	
	
	// adds choices that get neglected to a list of already neglected choices
	static public LinkedList<GameChoice> neglectChoices (LinkedList<GameChoice> eliminatedChoices, LinkedList<GameChoice> neglected){
		
		LinkedList<GameChoice> neglectedNow = new LinkedList<GameChoice>();
		
		for (GameChoice gameChoice : eliminatedChoices) {
			// wait can't be neglected
			if((gameChoice.getGameAsset() != null) && (gameChoice.getGameAsset() != GameAsset.SUPPLY_DEPOT) ){
				if(!neglected.contains(gameChoice)){
					neglectedNow.add(gameChoice);
				}
			}
		}
		
		neglectedNow.addAll(neglected);

		return neglectedNow;
	}
	
	static public LinkedList<GameChoice> unNeglectChoices (LinkedList<GameChoice> eliminatedChoices, LinkedList<GameChoice> neglected, GameState minTarget){
		LinkedList<GameChoice> neglectedNow = new LinkedList<GameChoice>();
		
//		LinkedList<GameAsset> minAssets = unAchivedAssetsInMinimumTarget(current, minTarget);
//		LinkedList<GameAsset> relevantAssets = getAssetsContributingToReachingTarget(current);
		
		for (GameChoice gameChoice : neglected) {
			if((gameChoice.getGameAsset() != null) && (gameChoice.getGameAsset() != GameAsset.SUPPLY_DEPOT) ){
				if(minTarget.getNumberOfAsset(gameChoice.getGameAsset()) == 0){
					neglectedNow.add(gameChoice);
				}
			}
		}
		return neglectedNow;
	}
	
	static public LinkedList<GameChoice> getNotNeglectedChoices(ArrayList<GameChoice> choices,LinkedList<GameChoice> neglectedChoices) {
		LinkedList<GameChoice> notNeglectedChoices = new LinkedList<GameChoice>();
		
		boolean neglected = false;
		
		for (GameChoice gameChoice : choices) {
			for (GameChoice neglectedChoice : neglectedChoices) {
				if (gameChoice.equals(neglectedChoice)) {
					neglected = true;
				}
			}
			if (!neglected) {
				notNeglectedChoices.add(gameChoice);
			}
			neglected = false;
		}
		return notNeglectedChoices;
	}
	
	static public LinkedList<GameChoice> getAppropriateChoices(GameState current, ArrayList<GameChoice> choices, LinkedList<GameChoice> neglectedChoices, GameState minTarget, GameState maxTarget) {

		LinkedList<GameChoice> eliminatedChoices = new LinkedList<GameChoice>();

		LinkedList<GameChoice> notNeglectedChoices = getNotNeglectedChoices(choices, neglectedChoices);
		LinkedList<GameAsset> relevantAssets = getAssetsContributingToReachingTarget(current, maxTarget);
		
		
		if(relevantAssets.contains(GameAsset.MINERAL)){
			relevantAssets.remove(GameAsset.MINERAL);
		}
		
		if(relevantAssets.contains(GameAsset.VESPENE_GAS)){
			relevantAssets.remove(GameAsset.VESPENE_GAS);
		}

		for (GameChoice gameChoice : notNeglectedChoices) {
			if (gameChoice.getGameAsset() == null) {
				eliminatedChoices.add(gameChoice);
			}
			else if (gameChoice.getGameAsset() == GameAsset.SUPPLY_DEPOT) {
				
				if (relevantAssets.contains(GameAsset.SUPPLY_DEPOT)) {
					
					if (current.getTotalNumberOfAssets(GameAsset.SUPPLY_DEPOT) == 0) {
						//TODO: calculate exactly how much supply is still needed to reach the minimum target
						eliminatedChoices.add(gameChoice);
					}
					relevantAssets.remove(GameAsset.SUPPLY_DEPOT);
				}
				int depotUnderConstruction = current.getTotalNumberOfAssets(GameAsset.SUPPLY_DEPOT) - current.getNumberOfAsset(GameAsset.SUPPLY_DEPOT);
				if((current.getNumberOfAsset(GameAsset.SUPPLY) < 8 ) && (needAdditionalSupplyDepotToReachTarget(current, minTarget)) && (depotUnderConstruction == 0))
				{
					eliminatedChoices.add(gameChoice);
				}
				
			} else {
				
				for (GameAsset gameAsset : relevantAssets) {
					if (gameChoice.getGameAsset() == gameAsset) {
						if (current.getTotalNumberOfAssets(gameAsset) < maxTarget.getNumberOfAsset(gameAsset)) {
							eliminatedChoices.add(gameChoice);
						}
					}
				}
			}
		}
		return eliminatedChoices;
	}
	
	
	static public ArrayList<GameAsset> getDependentAssets (ArrayList<GameAsset> relevantAssets, GameAsset asset) {
		
		GameObject object = GameDataStore.getGameObjectFromGameAsset(asset);
		
		// For every dependency asset has
		for(Entry<GameAsset, Integer> dependentAsset : object.getDependencies().entrySet()) {
			// get the dependencies the dependency has
			relevantAssets = getDependentAssets(relevantAssets, dependentAsset.getKey());
		}
		
		// If we haven't yet added the dependency which this function was called with, add it to the array
		if(!relevantAssets.contains(asset)) {
			relevantAssets.add(asset);
		}
		
		return relevantAssets;
	}	

	/**
	 * Returns a GameAsset array for use in the functions below.
	 * Excludes game assets being built
	 * @param state
	 * @return
	 */
	static public GameAsset[] getGameAssetArrayFromGameState(GameState state) {
		ArrayList<GameAsset> assets = new ArrayList<GameAsset>();
		
		for(GameAsset asset : GameAsset.values()) {
			
			if(GameDataStore.getGameObjectFromGameAsset(asset) != null) {
				
    			for(int i=0; i<state.getNumberOfAsset(asset); i++) {
    				assets.add(asset);
    			}
			}
		}
		
		GameAsset[] gameAssets = new GameAsset[assets.size()];
		assets.toArray(gameAssets);
		
		return gameAssets ;
	}
	/**
	 * Includes those game assets being built currently
	 * @param state
	 * @return
	 */
	static public GameAsset[] getTotalGameAssetArrayFromGameState(GameState state) {
		ArrayList<GameAsset> assets = new ArrayList<GameAsset>();
		
		for(GameAsset asset : GameAsset.values()) 
		{
			if(GameDataStore.getGameObjectFromGameAsset(asset) != null) {
				
    			for(int i=0; i<state.getTotalNumberOfAssets(asset); i++) {
    				
    				assets.add(asset);
    			}
			}
		}
		return assets.toArray(new GameAsset[assets.size()]);
	}
	
	/**
	 * Returns the total mineral cost of all of the GameAsset's in the array
	 * @param assets
	 * @return total mineral cost
	 */
	static public int calculateTotalMineralsRequired(GameAsset[] assets) {
		int value = 0;
		
		for(int i=0; i<assets.length; i++) {
			value += GameDataStore.getGameObjectFromGameAsset(assets[i]).getMineralCost();
		}
		
		return value;
	}
	
	/**
	 * Returns the total vespene gas cost of all of the GameAsset's in the array
	 * @param assets
	 * @return total vespene gas cost
	 */
	static public int calculateTotalVespeneRequired(GameAsset[] assets) {
		int value = 0;
		
		for(int i=0; i<assets.length; i++) {
			value += GameDataStore.getGameObjectFromGameAsset(assets[i]).getVespeneCost();
		}
		
		return value;
	}
	
	/**
	 * Calculates the supply deficit in the GameAsset array passed to it
	 * @param assets
	 * @return Positive value if enough supply is there, or a negative value if there is a supply deficit
	 */
	static public int calculateSupplyDeficit(GameAsset[] assets) {
	    int value = 0;

	    for (int i = 0; i < assets.length; i++) {
	    	value -= GameDataStore.getGameObjectFromGameAsset(assets[i]).getSupply();
	    }

	    return value;
	}

	/**
	 * Returns the number of Supply Depots required
	 * @param assets
	 * @return total mineral cost
	 */
	static public int calculateAdditionalSupplyDepotsRequired(GameAsset[] assets) {
		int value = calculateSupplyDeficit(assets);
		
		if(value > 0) {
			return 0;
		}
		else {
			// is of type double just to make the calculation more readable
			double supplyFromDepot = (double)GameDataStore.getGameObjectFromGameAsset(GameAsset.SUPPLY_DEPOT).getSupply();
			return (int)Math.ceil(value / supplyFromDepot);
		}
	}
	
	static public boolean needAdditionalSupplyDepotToReachTarget(GameState state, GameState targetState) {	
		HashMap<GameAsset, Integer> requiredAssets = new HashMap<GameAsset, Integer>();
		
		for(GameAsset asset : GameAsset.values()) {
			if(targetState.getTotalNumberOfAssets(asset) > 0 || state.getTotalNumberOfAssets(asset) > 0) {
				// store the difference between the number of each asset in targetState and State.
				// if the difference is negative, only store zero.
				requiredAssets.put(asset, Math.max(targetState.getTotalNumberOfAssets(asset), state.getTotalNumberOfAssets(asset)));
			}
		}
		// Create a new GameState object with the requiredAssets HashMap
		// This step just makes it easier to use our helper functions.
		GameState intermediate = new GameState(requiredAssets);
		int depotsUnderConstruction = state.getTotalNumberOfAssets(GameAsset.SUPPLY_DEPOT) - state.getNumberOfAsset(GameAsset.SUPPLY_DEPOT);
		int additionalSupplyDepots = calculateAdditionalSupplyDepotsRequired(getTotalGameAssetArrayFromGameState(intermediate));
		
		// return true if additionalSupplyDepots is more than zero
		return ((additionalSupplyDepots - depotsUnderConstruction )> 0);
	}
	

}














